package fr.ants.controller.clock;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.SwingWorker;

import fr.ants.controller.Simulator;
import fr.ants.model.Grid;

/**
 * <p>
 * Clock of the simulator.
 * </p>
 * <p>
 * It has several states :
 * </p>
 * <ul>
 * <li>RUNNING</li>
 * <li>PAUSED</li>
 * <li>STOPPED</li>
 * 
 * @author Vianney DUPOY DE GUITARD
 */
public class Clock extends SwingWorker<Void, Void> implements ActionListener {

	/** Minimum delay between two ticks. */
	private static final Integer MIN_DELAY = 2;

	/** The ticker, and it's initial interval time (2^7). */
	private Ticker ticker = new Ticker((int) Math.pow(MIN_DELAY, 6), this);

	/** The clock state. */
	private ClockState state = ClockState.STOPPED;

	/**
	 * Lock object, shared between the ticker and the clock, to synchronize
	 * themself.
	 */
	private Object timerLock = new Object();

	/** The simulator, called to trigger asull algorithms. */
	private Simulator simulator;

	/**
	 * Creates a new clock.
	 * 
	 * @param grid
	 *            : the grid attached to the simulator.
	 */
	public Clock(Grid grid) {
		simulator = new Simulator(grid);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected Void doInBackground() throws InterruptedException {
		// Infinite loop.
		while (true) {
			// Do nothing until the timer notifies this thread.
			synchronized (timerLock) {
				timerLock.wait();
			}
			// Triggers a simulator cycle;
			simulator.execute();
			fireUpdate();
		}
	}

	/** Fires update event. */
	private void fireUpdate() {
		firePropertyChange("grid", null, simulator.getGrid());
	}
	
	/** Fires update event. */
	private void fireStateUpdate() {
		firePropertyChange("state", null, state);
	}

	/** Starts the clock. */
	public void start() {
		if (state == ClockState.STOPPED) {
			ticker.start();
			state = ClockState.RUNNING;
			fireStateUpdate();
		}
	}

	/** Stops the clock. */
	public void stop() {
		if (state == ClockState.RUNNING || state == ClockState.PAUSED) {
			ticker.stop();
			state = ClockState.STOPPED;
			fireUpdate();
			fireStateUpdate();
		}
	}

	/** Restarts the clock after a pause. */
	public void restart() {
		if (state == ClockState.PAUSED) {
			ticker.start();
			state = ClockState.RUNNING;
			fireStateUpdate();
		}
	}

	/** Suspend the clock. */
	public void pause() {
		if (state == ClockState.RUNNING) {
			ticker.stop();
			state = ClockState.PAUSED;
			fireStateUpdate();
		}
	}

	/** @return true if the clock is ticking. */
	public boolean isRunning() {
		return state == ClockState.RUNNING;
		
	}

	/** @return true if the clock is suspended. */
	public boolean isPaused() {
		return state == ClockState.PAUSED;
	}

	/**
	 * Update the delay between two ticks.
	 * 
	 * @param delay
	 *            : the new delay.
	 */
	public void setDelay(int delay) {
		ticker.setDelay(delay);
	}

	/**
	 * return the delay between two ticks.
	 */
	public int getDelay() {
		return ticker.getDelay();
	}

	/** @return the minimal delay possible. */
	public Integer getMinDelay() {
		return MIN_DELAY;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void actionPerformed(ActionEvent e) {
		// Notifies the algorithm it can continue.
		synchronized (timerLock) {
			timerLock.notify();
		}
	}

	public Simulator getSimulator() {
		return simulator;
	}

	/**
	 * 
	 * @return the state of the simulation
	 */
	public ClockState getSate(){
		return state;
	}
}